{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# 子空间搜索-量子变分本征求解器\n",
    "\n",
    "<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>\n",
    "\n",
    "## 概览\n",
    "\n",
    "- 在本案例中，我们将展示如何通过 Paddle Quantum 训练量子神经网络来求解量子系统的整个能量谱。\n",
    "\n",
    "- 首先，让我们通过下面几行代码引入必要的 library 和 package。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "source": [
    "import numpy\n",
    "from numpy import pi as PI\n",
    "import paddle \n",
    "from paddle import matmul\n",
    "from paddle_quantum.circuit import UAnsatz\n",
    "from paddle_quantum.utils import random_pauli_str_generator, pauli_str_to_matrix, dagger"
   ],
   "outputs": [],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-04-30T09:12:57.571029Z",
     "start_time": "2021-04-30T09:12:54.960356Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 背景\n",
    "\n",
    "- 量子计算在近期内备受瞩目的一个应用就是变分量子本征求解器（variational quantum eigensolver, VQE）[1-3].\n",
    "- VQE 是量子化学在近期有噪量子设备（NISQ device）上的核心应用之一，其中一个功能比较强大的版本是 subspace-search VQE（SSVQE） [4]，其核心是去求解一个物理系统的哈密顿量（Hamiltonian）的基态和**激发态**的性质。数学上，可以理解为求解一个埃尔米特矩阵（Hermitian matrix）的本征值及其对应的本征向量。该哈密顿量的本征值组成的集合我们称其为能谱（energy spectrum）。\n",
    "- 接下来我们将通过一个简单的例子学习如何通过训练量子神经网络解决这个问题，即求解出给定哈密顿量 $H$ 的能谱。\n",
    "\n",
    "## SSVQE 分析物理系统的基态和激发态的能量\n",
    "\n",
    "- 对于具体需要分析的分子，我们需要其几何构型（geometry）、电荷（charge）以及自旋多重度（spin multiplicity）等多项信息来建模获取描述系统的 Hamilton 量。具体的，通过我们内置的量子化学工具包可以利用 fermionic-to-qubit 映射的技术来输出目标分子的量子比特 Hamilton 量表示。\n",
    "- 作为简单的入门案例，我们在这里提供一个简单的随机双量子比特 Hamilton 量作为例子。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "source": [
    "N = 2  # 量子比特数/量子神经网络的宽度\n",
    "SEED = 14  # 固定随机种子"
   ],
   "outputs": [],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-04-30T09:12:57.580461Z",
     "start_time": "2021-04-30T09:12:57.574080Z"
    }
   }
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "source": [
    "# 生成用泡利字符串表示的随机哈密顿量\n",
    "numpy.random.seed(SEED)\n",
    "hamiltonian = random_pauli_str_generator(N, terms=10)\n",
    "print(\"Random Hamiltonian in Pauli string format = \\n\", hamiltonian)\n",
    "\n",
    "# 生成 Hamilton 量的矩阵信息\n",
    "H = pauli_str_to_matrix(hamiltonian, N)"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Random Hamiltonian in Pauli string format = \n",
      " [[0.9152074787317819, 'x1,y0'], [-0.2717604556798945, 'z0'], [0.3628495008719168, 'x0'], [-0.5050129214094752, 'x1'], [-0.6971554357833791, 'y0,x1'], [0.8651151857574237, 'x0,y1'], [0.7409989105435002, 'y0'], [-0.39981603921243236, 'y0'], [0.06862640764702, 'z0'], [-0.7647553733438246, 'y1']]\n"
     ]
    }
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-04-30T09:12:57.604652Z",
     "start_time": "2021-04-30T09:12:57.592553Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 搭建量子神经网络\n",
    "\n",
    "- 在实现 SSVQE 的过程中，我们首先需要设计量子神经网络（quantum neural network, QNN），也即参数化量子电路。在本教程中，我们提供一个预设的适用于双量子比特的通用量子电路模板。理论上，该模板具有足够强大的表达能力可以表示任意的双量子比特逻辑运算 [5]。具体的实现方式是需要 3 个 $CNOT$ 门加上任意 15 个单比特旋转门 $\\in \\{R_y, R_z\\}$。\n",
    "\n",
    "- 初始化其中的变量参数，${\\bf{\\theta}}$ 代表我们量子神经网络中的参数组成的向量，一共有 15 个参数。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "source": [
    "THETA_SIZE = 15  # 量子神经网络中参数的数量\n",
    "\n",
    "def U_theta(theta, N):\n",
    "    \"\"\"\n",
    "    U_theta\n",
    "    \"\"\"\n",
    "    # 按照量子比特数量/网络宽度初始化量子神经网络\n",
    "    cir = UAnsatz(N)\n",
    "    \n",
    "    # 调用内置的量子神经网络模板\n",
    "    cir.universal_2_qubit_gate(theta, [0, 1])\n",
    "\n",
    "    # 返回量子神经网络的电路\n",
    "    return cir"
   ],
   "outputs": [],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-04-30T09:12:57.673600Z",
     "start_time": "2021-04-30T09:12:57.664882Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 配置训练模型——损失函数\n",
    "\n",
    "- 现在我们已经有了数据和量子神经网络的架构，我们将进一步定义训练参数、模型和损失函数，具体的理论可以参考 [4]。\n",
    "\n",
    "- 通过作用量子神经网络 $U(\\theta)$ 在一组正交的初始态上（方便起见，可以取计算基 $\\{|00\\rangle, |01\\rangle, |10\\rangle, |11\\rangle \\}$），我们将得到输出态 $\\{\\left| {\\psi_1 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle, \\left| {\\psi_2 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle, \\left| {\\psi_3 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle, \\left| {\\psi_4 \\left( {\\bf{\\theta }} \\right)} \\right\\rangle \\}$。\n",
    "\n",
    "- 进一步，在 SSVQE 模型中的损失函数一般由每个输出量子态 $\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle$ 关于哈密顿量 $H$ 的能量期望值（expectation value）的加权求和给出。这里我们默认权重向量 $\\vec{w} = [4, 3, 2, 1]$。\n",
    "\n",
    "- 具体的损失函数（loss function）定义为：\n",
    "\n",
    "$$\n",
    "\\mathcal{L}(\\boldsymbol{\\theta}) = \\sum_{k=1}^{2^n}w_k*\\left\\langle {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right|H\\left| {\\psi_k \\left( {\\bf{\\theta }} \\right)} \\right\\rangle. \\tag{1}\n",
    "$$"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "source": [
    "class Net(paddle.nn.Layer):\n",
    "    def __init__(self, shape, dtype='float64'):\n",
    "        super(Net, self).__init__()\n",
    "        \n",
    "        # 初始化 theta 参数列表，并用 [0, 2*pi] 的均匀分布来填充初始值\n",
    "        self.theta = self.create_parameter(shape=shape,\n",
    "                                           default_initializer=paddle.nn.initializer.Uniform(low=0.0, high=2*PI),\n",
    "                                           dtype=dtype, is_bias=False)\n",
    "    \n",
    "    # 定义损失函数和前向传播机制\n",
    "    def forward(self, H, N):\n",
    "        \n",
    "        # 构造量子神经网络\n",
    "        cir = U_theta(self.theta, N)\n",
    "        U = cir.U\n",
    "        \n",
    "        # 计算损失函数\n",
    "        loss_struct = paddle.real(matmul(matmul(dagger(U), H), U))\n",
    "\n",
    "        # 输入计算基去计算每个子期望值，相当于取 U^dagger*H*U 的对角元 \n",
    "        loss_components = []\n",
    "        for i in range(len(loss_struct)):\n",
    "            loss_components.append(loss_struct[i][i])\n",
    "        \n",
    "        # 最终加权求和后的损失函数\n",
    "        loss = 0\n",
    "        for i in range(len(loss_components)):\n",
    "            weight = 4 - i\n",
    "            loss += weight * loss_components[i]\n",
    "        \n",
    "        return loss, loss_components, cir"
   ],
   "outputs": [],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-04-30T09:12:58.591349Z",
     "start_time": "2021-04-30T09:12:58.577120Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 配置训练模型——模型参数\n",
    "在进行量子神经网络的训练之前，我们还需要进行一些训练的超参数设置，主要是学习速率（learning rate, LR）、迭代次数（iteration, ITR。这里我们设定学习速率为 0.3，迭代次数为 50 次。读者不妨自行调整来直观感受下超参数调整对训练效果的影响。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "source": [
    "ITR = 100 # 设置训练的总迭代次数\n",
    "LR = 0.3 # 设置学习速率"
   ],
   "outputs": [],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-04-30T09:12:59.351881Z",
     "start_time": "2021-04-30T09:12:59.343240Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 进行训练\n",
    "- 当训练模型的各项参数都设置完成后，我们将数据转化为 PaddlePaddle 动态图中的张量，进而进行量子神经网络的训练。\n",
    "- 过程中我们用的是 Adam Optimizer，也可以调用 PaddlePaddle 中提供的其他优化器。\n",
    "- 我们可以将训练过程中的每一轮 loss 打印出来。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "source": [
    "paddle.seed(SEED)\n",
    "    \n",
    "# 我们需要将 numpy.ndarray 转换成 PaddlePaddle 支持的 Tensor\n",
    "hamiltonian = paddle.to_tensor(H)\n",
    "\n",
    "# 确定网络的参数维度\n",
    "net = Net(shape=[THETA_SIZE])\n",
    "\n",
    "# 一般来说，我们利用 Adam 优化器来获得相对好的收敛，\n",
    "# 当然你可以改成 SGD 或者是 RMS prop.\n",
    "opt = paddle.optimizer.Adam(learning_rate=LR, parameters=net.parameters())\n",
    "\n",
    "# 优化循环\n",
    "for itr in range(1, ITR + 1):\n",
    "\n",
    "    # 前向传播计算损失函数并返回估计的能谱\n",
    "    loss, loss_components, cir = net(hamiltonian, N)\n",
    "\n",
    "    # 在动态图机制下，反向传播极小化损失函数\n",
    "    loss.backward()\n",
    "    opt.minimize(loss)\n",
    "    opt.clear_grad()\n",
    "\n",
    "    # 打印训练结果\n",
    "    if itr % 10 == 0:\n",
    "        print('iter:', itr, 'loss:', '%.4f' % loss.numpy()[0])\n",
    "    if itr == ITR:\n",
    "        print(\"\\n训练后的电路：\")\n",
    "        print(cir)"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "iter: 10 loss: -4.5668\n",
      "iter: 20 loss: -5.3998\n",
      "iter: 30 loss: -5.6210\n",
      "iter: 40 loss: -5.8872\n",
      "iter: 50 loss: -5.9246\n",
      "iter: 60 loss: -5.9471\n",
      "iter: 70 loss: -5.9739\n",
      "iter: 80 loss: -5.9833\n",
      "iter: 90 loss: -5.9846\n",
      "iter: 100 loss: -5.9848\n",
      "\n",
      "训练后的电路：\n",
      "--U----x----Rz(-1.18)----*-----------------x----U--\n",
      "       |                 |                 |       \n",
      "--U----*----Ry(-0.03)----x----Ry(2.362)----*----U--\n",
      "                                                   \n"
     ]
    }
   ],
   "metadata": {
    "ExecuteTime": {
     "end_time": "2021-04-30T09:13:07.503094Z",
     "start_time": "2021-04-30T09:13:04.968574Z"
    }
   }
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 测试效果\n",
    "\n",
    "我们现在已经完成了量子神经网络的训练，我们将通过与理论值的对比来测试效果。\n",
    "- 理论值由 NumPy 中的工具来求解哈密顿量的各个本征值；\n",
    "- 我们将训练 QNN 得到的各个能级的能量和理想情况下的理论值进行比对。\n",
    "- 可以看到，SSVQE 训练输出的值与理想值高度接近。"
   ],
   "metadata": {}
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "source": [
    "def output_ordinalvalue(num):\n",
    "    r\"\"\"\n",
    "    Convert to ordinal value\n",
    "\n",
    "    Args:\n",
    "        num (int): input number\n",
    "\n",
    "    Return:\n",
    "        (str): output ordinal value\n",
    "    \"\"\"\n",
    "    if num == 1:\n",
    "        return str(num) + \"st\"\n",
    "    elif num == 2:\n",
    "        return str(num) + \"nd\"\n",
    "    elif num == 3:\n",
    "        return str(num) + \"rd\"\n",
    "    else:\n",
    "        return str(num) + 'th'\n",
    "\n",
    "for i in range(len(loss_components)):\n",
    "    if i == 0:\n",
    "        print('The estimated ground state energy is: ', loss_components[i].numpy())\n",
    "        print('The theoretical ground state energy is: ', numpy.linalg.eigh(H)[0][i])\n",
    "    else:\n",
    "        print('The estimated {} excited state energy is: {}'.format(\n",
    "            output_ordinalvalue(i), loss_components[i].numpy())\n",
    "        )\n",
    "        print('The theoretical {} excited state energy is: {}'.format(\n",
    "            output_ordinalvalue(i), numpy.linalg.eigh(H)[0][i])\n",
    "        )"
   ],
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "The estimated ground state energy is:  [-2.18762366]\n",
      "The theoretical ground state energy is:  -2.18790201165885\n",
      "The estimated 1st excited state energy is: [-0.13721024]\n",
      "The theoretical 1st excited state energy is: -0.13704127143749587\n",
      "The estimated 2nd excited state energy is: [0.85251457]\n",
      "The theoretical 2nd excited state energy is: 0.8523274042087416\n",
      "The estimated 3rd excited state energy is: [1.47231932]\n",
      "The theoretical 3rd excited state energy is: 1.4726158788876045\n"
     ]
    }
   ],
   "metadata": {}
  },
  {
   "cell_type": "markdown",
   "source": [
    "_______\n",
    "\n",
    "## 参考文献\n",
    "\n",
    "[1] Peruzzo, A. et al. A variational eigenvalue solver on a photonic quantum processor. [Nat. Commun. 5, 4213 (2014).](https://www.nature.com/articles/ncomms5213)\n",
    "\n",
    "[2] McArdle, S., Endo, S., Aspuru-Guzik, A., Benjamin, S. C. & Yuan, X. Quantum computational chemistry. [Rev. Mod. Phys. 92, 015003 (2020).](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.92.015003)\n",
    "\n",
    "[3] Cao, Y. et al. Quantum chemistry in the age of quantum computing. [Chem. Rev. 119, 10856–10915 (2019).](https://pubs.acs.org/doi/abs/10.1021/acs.chemrev.8b00803)\n",
    "\n",
    "[4] Nakanishi, K. M., Mitarai, K. & Fujii, K. Subspace-search variational quantum eigensolver for excited states. [Phys. Rev. Res. 1, 033062 (2019).](https://journals.aps.org/prresearch/pdf/10.1103/PhysRevResearch.1.033062)\n",
    "\n",
    "[5] Vatan, F. & Williams, C. Optimal quantum circuits for general two-qubit gates. [Phys. Rev. A 69, 032315 (2004).](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.69.032315)"
   ],
   "metadata": {}
  }
 ],
 "metadata": {
  "kernelspec": {
   "name": "python3",
   "display_name": "Python 3.8.10 64-bit ('paddle_quantum_test': conda)"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "interpreter": {
   "hash": "1ba3360425d54dc61cc146cb8ddc529b6d51be6719655a3ca16cefddffc9595a"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}